package com.raft.core.log.statemachine;

import java.io.IOException;
import java.io.OutputStream;

import javax.annotation.Nonnull;

import com.raft.core.log.snapshot.Snapshot;

/**
 * State machine.
 */
public interface StateMachine {

    /**获取LastApplied
     * @return
     */
    int getLastApplied();

    /**应用日志
     * <br/>是日志组件回调上层服务的主要方法，提供了状态机上下文、日志的索引、命令的二进制数据，以及第一条日志的索引，上层服务在applyLog方法的实现中应用命令、修改数据，并进行回复客户端等操作
     * @param context
     * @param index
     * @param commandBytes
     * @param firstLogIndex
     */
    void applyLog(StateMachineContext context, int index, @Nonnull byte[] commandBytes, int firstLogIndex);

    /**是否要生成日志快照
     * <br/>Should generate or not.
     *
     * @param firstLogIndex first log index in log files, may not be {@code 0}
     * @param lastApplied   last applied log index
     * @return true if should generate, otherwise false
     */
    boolean shouldGenerateSnapshot(int firstLogIndex, int lastApplied);

    /**
     * Generate snapshot to output.
     *
     * @param output output
     * @throws IOException if IO error occurred
     */
    void generateSnapshot(@Nonnull OutputStream output) throws IOException;

    void applySnapshot(@Nonnull Snapshot snapshot) throws IOException;

    /**关闭状态机
     */
    void shutdown();

	/**ReadIndexTask在过半节点的matchIndex达到readIndex之后，会调用该方法，传入请求id和readIndex。服务状态机在收到回调后，比较自己的lastApplied和readIndex
	 * @param requestId
	 * @param readIndex
	 */
	void onReadIndexReached(String requestId, int readIndex);

	/**在实现readIndex时，需要注意一个细节。lastApplied理论上应该是可以应用的普通日志，比如set命令的日志的索引，但是在引入ReadIndex之后，系统的commitIndex所在的位置
	 * <br/>可能不是普通日志。如果不做特殊处理，lastApplied就永远无法达到readIndex的位置(因为没有日志被应用)。
	 * <br/>为了简便，这里只推进lastApplied，服务状态机不触发日志快照的创建。这理论上没有什么大问题，一方面对于服务状态机来说，没有新的可应用日志，数据也没有变换，频繁创建日志快照
	 * <br/>也没有好处；另一方面no-op日志、集群变更日志毕竟是少数，不太可能发生只推进而不应用日志的情况，日志快照的创建肯定会在某个时刻被触发并处理。
	 * @param index
	 */
	void advanceLastApplied(int index);

}
